#include <elapi.h>
#include <critsec.h>
#include <elasysapi.h>
#include "TApplet.h"
#include "TAppletSink.cpp"
#include "_CCallbackSink_.cpp"
#include "TAppletSink_callbackparams.cpp"
#include "TApplet_callbackparams.cpp"
#include "_TApplet.cpp"

TApplet::TApplet()
{
#ifdef _DEBUG
    _m_nThreadId = 0;
#endif
    if (_m_pOwnerCallbackContext_) _m_pOwnerCallbackContext_->Release();

    if (_Impl_CheckHelperInfoFlag(HELPER_CARHOST_CALLING)) {
        _Impl_CallbackSink_GetDefaultCallbackContext(
                                &_m_pOwnerCallbackContext_);
    }
    else {
        _Impl_CallbackSink_InitCallbackContext(
                                &_m_pOwnerCallbackContext_);
    }
    _m_pOwnerApplet = (IApplet*)this;
    _m_HeadOfStorageList.Initialize();
    _m_HeadOfEnvVarList.Initialize();

    _m_pDomain = (IApplet*)this;
}

TApplet::~TApplet()
{
    if (_m_pThread) {
        Finish(AppletFinish_ASAP);
        _m_pThread->Release();
        _m_pThread = NULL;
    }

    if (_m_pOwnerCallbackContext_) {
        _m_pOwnerCallbackContext_->Release();
        _m_pOwnerCallbackContext_ = NULL;
    }

    _CMapNode* pEnvNode;
    while (!_m_HeadOfEnvVarList.IsEmpty()) {
        pEnvNode = (_CMapNode*)_m_HeadOfEnvVarList.Next();
        pEnvNode->Detach();
        _Impl_CMap_FreeNode(pEnvNode);
    }

    _CMapNode* pDirNode;
    while (!_m_HeadOfStorageList.IsEmpty()) {
        pDirNode = (_CMapNode*)_m_HeadOfStorageList.Next();
        pDirNode->Detach();
        _Impl_CMap_FreeNode(pDirNode);
    }
}

Void AppletQuitRoutine(PVoid pArgcv)
{
    BufferOf<WString>* p = (BufferOf<WString>*)pArgcv;
    for (Int32 n = 0; n < p->GetUsed(); n++) {
        assert(!(*p)[n].IsNull());
        WString::Free((*p)[n]);
    }
    BufferOf<WString>::Free(p);
}

typedef struct {
    TApplet* pThis;
    BufferOf<WString>* pArgcv;
} ThreadParams, *PThreadParams;

ECode _Impl_TApplet_Create(PVoid * ppAppletObj, PInterface * ppCallbackContext)
{
    TApplet* pApplet = NULL;


    ECode ec = TApplet::NewByFriend(&pApplet);
    if (FAILED(ec)) return ec;

    pApplet->_m_pThread = CThread::GetCurrent();
    pApplet->_m_pThread->AddRef();
    *ppAppletObj = pApplet;

    CObject::ReleaseAtProcessExit((IApplet*)pApplet);

    pApplet->_m_pOwnerCallbackContext_->AddRef();
    *ppCallbackContext = pApplet->_m_pOwnerCallbackContext_;

    _CThread_SetLocalValue(TL_APPLET_DIR_SLOT, NULL);

    return NOERROR;
}

ECode _Impl_TApplet_SetAppletPath(PVoid pAppletObj, WStringBuf* pAppletPath)
{
    TApplet* pThis = (TApplet*)pAppletObj;

    ECode ec = pThis->SetCurrentDirectory(*pAppletPath);

    return ec;
}

ECode _Impl_TApplet_Prepare(PVoid pAppletObj)
{
    TApplet* pThis = (TApplet*)pAppletObj;
#ifdef _DEBUG
    CThread::GetCurrent()->GetId(&pThis->_m_nThreadId);
#endif

    ECode ec = _Impl_CAppletContext_Initialize((IApplet*)pThis);
    if (FAILED(ec)) return ec;
    
    IThread *pThread = pThis->_m_pThread;
    ec = pThis->NotifyThreadCreated(pThread);
    assert(SUCCEEDED(ec));

    pThis->TApplet::Callback::Started();

    // Use applet instead of domain before we implement real domain
    CThread::SetLocalValue(TL_DOMAIN_SLOT, (TLValue)(IApplet*)pThis);

    return NOERROR;
}

ECode _Impl_TApplet_DoWork(PVoid pAppletObj, ECode ec)
{
    TApplet* pThis = (TApplet*)pAppletObj;

    pThis->TApplet::Callback::MainCompleted(ec);
    if (NOERROR_EXIT == ec) ec = NOERROR;
    else if (SUCCEEDED(ec)) ec = _Impl_CallbackSink_TryToHandleEvents();

    _Impl_CAppletContext_Uninitialize();
    pThis->TApplet::Callback::Finishing();
    return NOERROR;
}

ECode TApplet::AppletRoutine(PVoid pParam)
{
    ECode ec;
    BufferOf<WString>* pArgcv = ((PThreadParams)pParam)->pArgcv;
    TApplet* pThis = ((PThreadParams)pParam)->pThis;

    assert(pArgcv);
    assert(pThis);
    delete (PThreadParams)pParam;

    _CThread_SetLocalValue(TL_APPLET_DIR_SLOT, pThis->_m_wsCurrentPath.GetPayload());

    CThread::AtQuit(AppletQuitRoutine, pArgcv);
    IAppletTaskSpecific* pTaskSpecific = IAppletTaskSpecific::Probe((IApplet*)pThis);
    assert (pTaskSpecific);
    pTaskSpecific->AddRef();

    ec = _Impl_TApplet_Prepare(pThis);
    if (FAILED(ec)) goto Exit;

    ec = pThis->Main(*pArgcv);

    ec = _Impl_TApplet_DoWork(pThis, ec);

Exit:
    //pTaskSpecific->Aggregate(AggrType_Unaggregate, (PInterface)&ECLSID_TApplet);
    pTaskSpecific->Release();

    pThis->Release();

    return ec;
}

ECode TApplet::Start(
    const BufferOf<WString> & args)
{
    ECode _ec_ = NOERROR;
    PThreadParams pParams;
    Int32 nArgc;
    Int32 n;
    PInterface _pCurtCallbackContext_;
    PInterface pOrgAppletContext; 

    _SwitchCallbackContextToSelf(&_pCurtCallbackContext_);

    _AspectLock();

    if (_m_pThread) {
        _ec_ = E_APPLET_ALREADY_STARTED;
        goto Exit;
    }

    // Create a new thread and run Applet::Main() in the new thread
    pParams = new ThreadParams;
    pParams->pThis = this;
    ((_IInterface*)this)->AddRef();

    pParams->pArgcv = args.Clone();
    nArgc = pParams->pArgcv->GetUsed();

    for (n = 0; n < nArgc; n++) {
        pParams->pArgcv->SetUsed(n + 1);
        (*pParams->pArgcv)[n] = WString::Duplicate(args[n]);
        if ((*pParams->pArgcv)[n].IsNull()) {
            nArgc = n;
            _ec_ = E_OUT_OF_MEMORY;
            break;
        }
    }

    if (SUCCEEDED(_ec_)) {
        if (_Impl_CheckHelperInfoFlag(HELPER_CARHOST_CALLING)) {
            _Impl_SetHelperInfoFlag(HELPER_CARHOST_CALLING, FALSE);
            _m_pThread = CThread::GetCurrent();
            _m_pThread->AddRef();
            _AspectUnlock();
            _ec_ = AppletRoutine(pParams);
            goto Exit2;
        }
        else {
            _CThread_GetLocalObject(TL_APPLET_SLOT, &pOrgAppletContext);
            _CThread_SetLocalObject(TL_APPLET_SLOT, NULL);

            _ec_ = CThread::New(AppletRoutine, (PVoid)pParams, ThreadCreationFlag_Unstarted, &_m_pThread);
            if (SUCCEEDED(_ec_)) _ec_ = _m_pThread->Start();

            _CThread_SetLocalObject(TL_APPLET_SLOT, pOrgAppletContext);
        }

    }
    if (FAILED(_ec_)) {
        for (n = 0; n < nArgc; n++) {
            assert(!(*pParams->pArgcv)[n].IsNull());
            WString::Free((*pParams->pArgcv)[n]);
        }
        BufferOf<WString>::Free(pParams->pArgcv);
        delete pParams;
        ((_IInterface*)this)->Release();
    }
Exit:
    _AspectUnlock();
Exit2:
    _SwitchCallbackContextToOriginal(_pCurtCallbackContext_);
    return _ec_;
}

ECode TApplet::Finish(
    AppletFinish finishflag)
{
    ECode _ec_ = NOERROR;
    PInterface _pCurtCallbackContext_;

    _SwitchCallbackContextToSelf(&_pCurtCallbackContext_);

    if (_m_pThread) {
        if (AppletFinish_Nice == finishflag && __m_pOuter) _ec_ = Callback::UpdatingRequestToFinishWithPriority(CallbackPriority_Highest);
        else _ec_ = _Impl_CallbackSink_RequestToFinish(
                                        _m_pOwnerCallbackContext_, finishflag);
    } else {
        _ec_ = E_APPLET_NOT_STARTED;
    }

    _SwitchCallbackContextToOriginal(_pCurtCallbackContext_);
    return _ec_;
}

ECode TApplet::GetMainThread(
    IThread **thread)
{
    if (NULL == thread) return E_INVALID_ARGUMENT;

    if (_m_pThread) _m_pThread->AddRef();
    *thread = _m_pThread;

    return NOERROR;
}

ECode TApplet::GetAllThreads(
    IObjectEnumerator **threads)
{
    if (NULL == threads) {
        return E_INVALID_ARGUMENT;
    }

    _m_threadContainer.GetEnumerator(threads);

    return NOERROR;
}

ECode TApplet::WaitUntilFinished(
    Millisecond32 timeout,
    WaitResult *result)
{
    if (_m_pThread) return _m_pThread->Join(timeout, result);
    else return E_APPLET_NOT_STARTED;

    return NOERROR;
}

ECode TApplet::GetStatus(
    AppletStatus* status)
{
    if (status == NULL) return E_INVALID_ARGUMENT;
    *status = _Impl_CallbackSink_GetStatus(_m_pOwnerCallbackContext_);
    return NOERROR;
}

ECode TApplet::SetCurrentDirectory(
    WString path)
{
    _m_wsCurrentPath.Copy(path);

    Int32 index = path.IndexOf(L':');

    if (index > 0) {
        WStringBuf_<128> storage;

        path.Substring(0, index+1, storage);

        _CMapNode* pNode, *pNewNode;

        pNewNode = _Impl_CMap_AllocNode(storage, path);
        if (pNewNode == NULL) {
            return E_OUT_OF_MEMORY;
        }

        ForEachDLinkNode(_CMapNode*, pNode, &_m_HeadOfStorageList) {
            if (storage.Compare(pNode->m_pwcsKey, StringCase_Insensitive) == 0) {
                pNode->Detach();
                _Impl_CMap_FreeNode(pNode);
                break;
            }
        }

        _m_HeadOfStorageList.InsertPrev(pNewNode);
    }

    return NOERROR;
}

ECode TApplet::GetCurrentDirectory(
    WStringBuf * path)
{
    if (path == NULL) return E_INVALID_ARGUMENT;

    path->Copy(_m_wsCurrentPath);
    return NOERROR;
}

ECode TApplet::GetCurrentDirectoryInStorage(
    WString storageName,
    WStringBuf * path)
{
    if (path == NULL) return E_INVALID_ARGUMENT;
    if (storageName.IsNullOrEmpty()) return E_INVALID_ARGUMENT;
    if (storageName.GetChar(storageName.GetLength()-1) != L':') return E_INVALID_ARGUMENT;

    _CMapNode* pNode;
    ForEachDLinkNode(_CMapNode*, pNode, &_m_HeadOfStorageList) {
        if (storageName.Compare(pNode->m_pwcsKey, StringCase_Insensitive) == 0) {
            path->Copy(pNode->m_pwcsValue);
            return NOERROR;
        }
    }

    return E_DOES_NOT_EXIST;
}

ECode TApplet::SetEnvironmentVariable(
    WString name,
    WString value)
{
    if (name.IsNullOrEmpty() || value.IsNullOrEmpty()) return E_INVALID_ARGUMENT;
    if (name.GetLength() > 127 || value.GetLength() > 511) return E_INVALID_ARGUMENT;

    _CMapNode* pEnv = _Impl_CMap_AllocNode(name, value);
    if (pEnv == NULL) return E_OUT_OF_MEMORY;

    _CMapNode* pNode;
    ForEachDLinkNode(_CMapNode*, pNode, &_m_HeadOfEnvVarList) {
        if (name.Compare(pNode->m_pwcsKey, StringCase_Insensitive) == 0) {
            pNode->Detach();
            _Impl_CMap_FreeNode(pNode);
            break;
        }
    }
    _m_HeadOfEnvVarList.InsertPrev(pEnv);

    return NOERROR;
}

ECode TApplet::GetEnvironmentVariable(
    WString name,
    WStringBuf* value)
{
    if (name.IsNullOrEmpty() || value == NULL) return E_INVALID_ARGUMENT;
   _CMapNode* pNode;

    ForEachDLinkNode(_CMapNode*, pNode, &_m_HeadOfEnvVarList) {
        if (name.Compare(pNode->m_pwcsKey, StringCase_Insensitive) == 0) {
            value->Copy(pNode->m_pwcsValue);
            return NOERROR;
        }
    }

    return E_DOES_NOT_EXIST;
}

ECode TApplet::ObjectEnter(
    /* [in] */ PInterface pObject)
{
    if (pObject == NULL) return E_INVALID_ARGUMENT;

    ECode ec;

    ec = this->OnObjectEntering(pObject);
    if (FAILED(ec)) return ec;

    _Impl_EnterProtectedZone();
    ec = this->OnObjectEntered(pObject);
    pObject->Aggregate(AggrType_EnteredContext, (PInterface)(PContext)this);
    _Impl_LeaveProtectedZone();

    return ec;
}

ECode TApplet::ObjectLeave(
    /* [in] */ PInterface pObject)
{
    if (pObject == NULL) return E_INVALID_ARGUMENT;

    ECode ec;

    _Impl_EnterProtectedZone();
    ec = this->OnObjectLeaving(pObject);
    _Impl_LeaveProtectedZone();
    if (FAILED(ec)) return ec;

    pObject->Aggregate(AggrType_LeftContext, (PInterface)(PContext)this);
    this->OnObjectLeft(pObject);

    return NOERROR;
}

ECode TApplet::CreateObject(
    /* [in] */ const ClassID & clsid,
    /* [in] */ const InterfaceID & iid,
    /* [out] */ PInterface * ppObject)
{
    PInterface pObject = NULL;

    if (ppObject == NULL) return E_INVALID_ARGUMENT;

    ECode ec = _CObject_CreateInstance(clsid, CTX_SAME_DOMAIN, iid, &pObject );
    if (FAILED(ec)) return ec;

    ec = ObjectEnter(pObject);
    if (FAILED(ec)) pObject->Release();
    else *ppObject = pObject;

    return ec;
}

ECode TApplet::SetPriorityRank(
    /* [in] */ ThreadPriorityRank priorityRank)
{
    return E_NOT_IMPLEMENTED;
}

ECode TApplet::GetPriorityRank(
    /* [out] */ ThreadPriorityRank *priorityRank)
{
    return E_NOT_IMPLEMENTED;
}

ECode TApplet::GetStartTime(
    /* [out] */ SystemTime *time)
{
    return E_NOT_IMPLEMENTED;
}

ECode TApplet::Idle()
{
    return E_NOT_IMPLEMENTED;
}

ECode TApplet::Main(const BufferOf<WString> & args)
{
    return E_NOT_IMPLEMENTED;
}

ECode TApplet::Activate()
{
    IAppletTaskSpecific* pTaskSpecific = IAppletTaskSpecific::Probe((IApplet*)this);
    if (pTaskSpecific) return pTaskSpecific->Activate();

    return E_NOT_IMPLEMENTED;
}

ECode TApplet::Deactivate()
{
    IAppletTaskSpecific* pTaskSpecific = IAppletTaskSpecific::Probe((IApplet*)this);
    if (pTaskSpecific) return pTaskSpecific->Deactivate();

    return E_NOT_IMPLEMENTED;
}

ECode TApplet::IsActivated(
        /* [out] */ Boolean *activated)
{
    IAppletTaskSpecific* pTaskSpecific = IAppletTaskSpecific::Probe((IApplet*)this);
    if (pTaskSpecific) return pTaskSpecific->IsActivated(activated);

    return E_NOT_IMPLEMENTED;
}

ECode TApplet::Snooze()
{
    IAppletTaskSpecific* pTaskSpecific = IAppletTaskSpecific::Probe((IApplet*)this);
    if (pTaskSpecific) return pTaskSpecific->Snooze();

    return E_NOT_IMPLEMENTED;
}

ECode TApplet::Wakeup()
{
    IAppletTaskSpecific* pTaskSpecific = IAppletTaskSpecific::Probe((IApplet*)this);
    if (pTaskSpecific) return pTaskSpecific->Wakeup();

    return E_NOT_IMPLEMENTED;
}

ECode TApplet::GetName(
        /* [out] */ WStringBuf * name)
{
    IAppletTaskSpecific* pTaskSpecific = IAppletTaskSpecific::Probe((IApplet*)this);
    if (pTaskSpecific) return pTaskSpecific->GetName(name);

    return E_NOT_IMPLEMENTED;
}

ECode TApplet::GetAllForms(
    IObjectEnumerator **forms)
{
    ECode ec = E_DOES_NOT_EXIST;

    if (NULL == forms) {
        return E_INVALID_ARGUMENT;
    }

    *forms = NULL;

    if (NULL != _m_pAppletGUI) {
        ec = _m_pAppletGUI->GetAllForms(forms);
    }

    return ec;
}

ECode TApplet::SetAppletGUI(
    IAppletGUI *appletGUI)
{
    if (NULL == appletGUI) {
        return E_INVALID_ARGUMENT;
    }

    _m_pAppletGUI = appletGUI;

    return NOERROR;
}

ECode TApplet::NotifyActivated(
    Flags32 cFlag)
{
    return Callback::ActivatedWithPriority(cFlag);
}

ECode TApplet::NotifyDeactivated(
    Flags32 cFlag)
{
    return Callback::DeactivatedWithPriority(cFlag);
}

ECode TApplet::NotifyLayoutReady(
    Flags32 cFlag)
{
    return Callback::LayoutReadyWithPriority(cFlag);
}

ECode TApplet::NotifyThreadCreated(
    IThread *thread)
{
    if (NULL == thread) {
        return E_INVALID_ARGUMENT;
    }

    _m_threadContainer.Add((IInterface *)thread);

    return NOERROR;
}

ECode TApplet::NotifyThreadQuit(
    IThread *thread)
{
    if (NULL == thread) {
        return E_INVALID_ARGUMENT;
    }

    _m_threadContainer.Remove((IInterface *)thread);

    return NOERROR;
}

